C++ 链接报错:Dso missing from command line
工具链升级(例如从 GCC 9 换到 GCC 11)之后,代码没有改动,链接期却报出 Dso missing from command line。本文写给碰到这类报错的 C/C++ 开发者:先解释这条报错背后的链接器机制,再给出从正确到临时绕过的几种修法,读完可以独立定位并修复同类问题。
问题现象
GCC 11.4 下编译链接报错:
/lib/aarch64-linux-gnu/libfmt.so.8: error adding symbols: Dso missing from command line 同样的代码和链接方式在 GCC 9.4 下编译正常。
原因
这条报错来自 GNU ld 的既定行为,不是 GCC 11 引入的新策略。两个长期机制叠加在一起:
- 不解析间接依赖的符号。binutils 2.22(2011 年)起,ld 默认不再从间接依赖的动态库解析符号(
--no-copy-dt-needed-entries成为默认)。代码引用了某个库的符号,而这个库没有显式出现在链接命令里、只作为其他库的间接依赖存在时,ld 会拒绝解析,直接报Dso missing from command line。 - 发行版的
--as-needed默认补丁。上游 GNU ld 的默认值是--no-as-needed,但 Debian/Ubuntu 长期给自家的 ld 打了--as-needed默认补丁:命令行里列出但未被直接引用的库不会写进 DT_NEEDED,进一步压缩了隐式依赖被顺带带上的空间。
在此基础上,ld 对命令行做从左到右的单向扫描、不回溯,所以库不仅要在命令行里,位置也必须正确:-l 选项要放在引用它符号的目标文件之后。
至于「为什么旧工具链能通过、新的不能」,具体触发点随环境而异——链接顺序、依赖库的打包方式、GCC 驱动传给链接器的参数差异都可能参与其中。旧链接能通过,依赖的是宽松时代工具链的偶然行为,并不是受保证的兼容语义;新工具链只是不再容忍这种写法。
解决方案
1. 显式补上缺失的库(推荐)
g++ ... main.o other.o -lfmt -lfmt 必须放在引用它的目标文件之后,顺序反了同样会报错。
2. 用 pkg-config 生成链接参数
g++ ... main.o other.o $(pkg-config --libs fmt) 适合库自带 .pc 文件的场景,避免手写库列表。
3. CMake
find_package(fmt REQUIRED)
target_link_libraries(your_target PRIVATE fmt::fmt) 导入目标由 CMake 负责展开成正确的链接顺序。
4. 间接依赖同样要显式声明
代码没有直接使用 fmt,但链接的某个库依赖它,也属于同一种情形:
g++ ... main.o -lyourlib -lfmt -lfmt 放在 -lyourlib 之后。
5. 临时绕过(不推荐)
g++ ... -Wl,--no-as-needed -lfmt -Wl,--copy-dt-needed-entries 可以恢复 binutils 2.22 之前「从间接依赖解析符号」的旧行为,同样只应作临时绕过。这两个开关都掩盖了「依赖没有显式声明」这一根本问题,留给后续升级的隐患大于收益。
总结
Dso missing from command line 的根因是隐式依赖:代码用到的库没有以正确的顺序显式出现在链接命令里。这是 ld 多年来的规范行为,新工具链只是不再放行偶然能跑的链接方式。正确做法是把缺失的库补进链接命令(或交给 pkg-config/CMake 生成),而不是用 --no-as-needed、--copy-dt-needed-entries 之类的开关绕过。
参考
- Stack Overflow: Linking error: DSO missing from command line
- Stack Overflow: Understanding Linker Error: DSO missing from command line
- Stack Overflow: Why does Ubuntu's linker use --as-needed by default?
- Gentoo Wiki: Project:Quality Assurance/As-needed
- GNU ld 手册:Options(--as-needed / --copy-dt-needed-entries)